import Parser from './parser';
import util from './util';
import _ from 'lodash';
class Regexper {
  constructor(root) {
    this.root = root;
    this.buggyHash = false;
    this.form = root.querySelector('#regexp-form');
    this.field = root.querySelector('#regexp-input');
    this.error = root.querySelector('#regexp-error');
    this.warnings = root.querySelector('#regexp-warnings');

    this.download = root.querySelector('a[data-action="download"]');

    this.svgContainer = root.querySelector('#regexp-render');
    this.init();

  }
  init(){
    this.detectBuggyHash();
    this.bindListeners();
    util.tick().then(() => {
      window.dispatchEvent(util.customEvent('hashchange'));
    });
  }
  keypressListener(ev) {
    // shift + enter 提交表单
    if (ev.shiftKey && ev.keyCode === 13) {
      ev.preventDefault();
      this.form.dispatchEvent(util.customEvent('submit'));
    }
  }

  documentKeypressListener(ev) {
    // Pressing escape will cancel a currently running render.
    if (ev.keyCode === 27 && this.running) {
      this.running.cancel();
    }
  }

  // Event handler for submission of the regular expression. Changes the URL
  // hash which leads to the expression being rendered.
  submitListener(event) {
    event.returnValue = false;
    if (event.preventDefault) {
      event.preventDefault();
    }

    try {
      this._setHash(this.field.value);
    }
    catch(e) {
      // Failed to set the URL hash (probably because the expression is too
      // long). Turn off display of the permalink and just show the expression.
      this.showExpression(this.field.value);
    }
  }

  // Event handler for URL hash changes. Starts rendering of the expression.
  hashchangeListener() {
    let expr = this._getHash();

    if (expr instanceof Error) {
      this.state = 'has-error';
      this.error.innerHTML = 'Malformed expression in URL';
      util.track('send', 'event', 'visualization', 'malformed URL');
    } else {
      this.showExpression(expr);
    }
  }
  // Binds all event listeners.
  bindListeners() {
    this.field.addEventListener('keypress', this.keypressListener.bind(this));
    this.form.addEventListener('submit', this.submitListener.bind(this));
    this.root.addEventListener('keyup', this.documentKeypressListener.bind(this));
    window.addEventListener('hashchange', this.hashchangeListener.bind(this));
  }

  _setHash(hash){
    location.hash = encodeURIComponent(hash);
  }
  _getHash(){
    try{
      const hash = location.hash.slice(1);
      return this.buggyHash ? hash: decodeURIComponent(hash);
    }catch (e){
      return e;
    }
  }


  // Currently state of the application. Useful values are:
  //  - `''` - State of the application when the page initially loads
  //  - `'is-loading'` - Displays the loading indicator
  //  - `'has-error'` - Displays the error message
  //  - `'has-results'` - Displays rendered results
  set state(state){
    this.root.className = state;
  }
  get state(){
    return this.root.className;
  }

  showExpression(expression){
    this.field.value = expression;
    this.state = '';
    if(expression !== ''){
      this.renderRegexp(expression);
    }
  }
  buildBlobURL(content){
    return URL.createObjectURL(new Blob([content],{type: 'image/svg+xml'}));
  }
  // Detect if https://bugzilla.mozilla.org/show_bug.cgi?id=483304 is in effect
  detectBuggyHash() {
    if (typeof window.URL === 'function') {
      try {
        let url = new URL('http://regexper.com/#%25');
        this.buggyHash = (url.hash === '#%');
      }
      catch(e) {
        this.buggyHash = false;
      }
    }
  }
  updateLinks(){
    try{
      this.download.download = 'image.svg';
      this.download.href = this.buildBlobURL(this.svgContainer.querySelector('.svg').innerHTML);
    }catch(e) {
      this.download.style.display = 'none';
    }
  }
  displayWarnings(warnings){
    this.warnings.innerHTML = _.map(warnings,warning => {
      `<li class="inline-icon">$(util.icon("#warning")}${warning}</li>`;
    }).join('');
  }
  renderRegexp(expression) {
    let parseError = false, startTime, endTime;

    //When a render is already in progress, cancel it and try rendering again
    //after a short delay (canceling a render is not instantaneous).
    if (this.running) {
      this.running.cancel();
      return util.wait(10).then(() => this.renderRegexp(expression));
    }

    this.state = 'is-loading';
    util.track('send', 'event', 'visualization', 'start');
    startTime = new Date().getTime();
    this.running = new Parser(this.svgContainer);

    return this.running.parse(expression).catch(message => {
      this.state = 'has-error';
      this.error.innerHTML = '';
      this.error.appendChild(document.createTextNode(message));
      parseError = true;
      throw message;
    }).then(parser => parser.render()).then(() => {
      this.state = 'has-results';
      this.updateLinks();
      this.displayWarnings(this.running.warnings);
      util.track('send', 'event', 'visualization', 'complete');
      endTime = new Date().getTime();
      util.track('send', 'timing', 'visualization', 'total time', endTime -
          startTime);
    })
    // Handle any errors that happened during the rendering pipeline.
    // Swallows parse errors and render cancellations. Any other exceptions
    // are allowed to continue on to be tracked by the global error handler.
      .catch(message => {
        if (message === 'Render cancelled') {
          util.track('send', 'event', 'visualization', 'cancelled');
          this.state = '';
        } else if (parseError) {
          util.track('send', 'event', 'visualization', 'parse error');
        }else{
          throw message;
        }
      }).then(() => {
        this.running = false;
      },
      message => {
        this.running = false;
        throw message;
      });
  }
}

window.reg = new Regexper(document.querySelector('#app'));